package com.company.bookstore.security;

import com.alibaba.fastjson.JSONObject;
import com.company.bookstore.common.constant.RequestConstant;
import com.company.bookstore.common.jwt.JwtUtil;
import com.company.bookstore.common.redis.RedisUtil;
import com.company.bookstore.common.result.MvcResult;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.security.sasl.AuthenticationException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * UsernamePasswordFilter
 *
 * 主实现逻辑
 * 用户登录操作
 *
 * 指定url
 *
 * 账号密码登录
 *
 * redis没有token数据，且登录成功
 *
 *  服务器端设置Security信息 role为null 具有username；   password 数据（暂时的）
 *
 *
 *
 *  存储redis token 有效期 一天
 *
 *  token内置有效期 12小时
 *
 *  token 内的role 为null
 *
 *  返回token 给用户
 *
 *
 * redis有token数据，且登录成功
 *
 *
 * 	token过期，即12小时过去了，token有过期的数据：
 * 		刷新token ，再存到redis上，返回新token给用户
 *
 *
 * 	不过期
 * 		返回旧token给用户
 *
 * {@link AbstractAuthenticationProcessingFilter}
 *
 * @author 吴经纶
 * @blame Android Team
 */
@Slf4j
public  class UsernamePasswordFilter extends AbstractAuthenticationProcessingFilter {

        private final AuthenticationManager authenticationManager;
        private final RedisUtil redisUtil;
        /**
         * 匹配登录网址进行登录逻辑
         *
         * 子类构造时默认调用父类构造函数
         *
         * */

        public UsernamePasswordFilter( AuthenticationManager authenticationManager,RedisUtil redisUtil) {

            super(new AntPathRequestMatcher(RequestConstant.HTTP_LOGIN_URI, "POST"));
            this.authenticationManager = authenticationManager;
            this.redisUtil = redisUtil;
        }

    @Override
    protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
        String token = request.getHeader(RequestConstant.HTTP_LOGIN_JWT_HEADER);
        if (token!=null){
            return false;
        }
        return super.requiresAuthentication(request,response);
    }

    protected String obtainUsername(HttpServletRequest request) {
            String username = request.getParameter("username");
            if (null == username){

                logger.info("username字段为空");
                return "";
            }
            return username;
        }

        protected String obtainPassword(HttpServletRequest request) {
            String password = request.getParameter("password");
            if (null == password){
                logger.info("password字段为空");

                return "";
            }
            return password;
        }

        /**
         *  在authrequest 中注入 session，addr 信息
         * */
        protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
            authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
        }

        /**
         * 重写attemptAuthentication 函数
         *
         * 构造一个空的token 给下一环节验证
         *
         * attemptAuthentication 的模板方法
         *
         * 递归调用认证方法getAuthenticationManager().authenticate
         *
         * {@link org.springframework.security.authentication.AuthenticationManager}
         *
         * {@link org.springframework.security.authentication.ProviderManager}
         *
         * 管理 {@link UsernamePasswordProvider} （自定义）
         * 使用内部 support 方法 筛选对应的解决方案
         *
         *
         * */
        @Override
        public Authentication attemptAuthentication (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException {
//            来个注释
            httpServletResponse.setCharacterEncoding("utf-8");
            httpServletResponse.setContentType("application/json;utf-8");
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");  // 允许跨域的地址为所有
            httpServletResponse.addHeader("Access-Control-Max-Age","3600");  // 非简单请求，只要第一次通过OPTIONS检查 在1小时之内不会在调用OPTIONS进行检测
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpServletResponse.setHeader("Access-Control-Allow-Headers" ,"Origin, X-Requested-With, Content-Type, Accept,login_token");
            if ( !"POST".equals(httpServletRequest.getMethod())) {
                throw new AuthenticationServiceException("Authentication method not supported: " + httpServletRequest.getMethod());
            } else {
                String username = obtainUsername(httpServletRequest);
                String password = obtainPassword(httpServletRequest);
                username = username.trim();
                UsernamePasswordAuthenticationToken authRequest;
                if (httpServletRequest.getRequestURI().endsWith(RequestConstant.HTTP_LOGIN_USER_URI)) {
                    authRequest = new UsernamePasswordToken(username, password);
                }else if(httpServletRequest.getRequestURI().endsWith(RequestConstant.HTTP_LOGIN_ADMIN_URI)){
                    authRequest = new AdminnamePasswordToken(username, password);
                }else{
                    return null;
                }
                this.setDetails(httpServletRequest, authRequest);
                return authenticationManager.authenticate(authRequest);
            }
        }
/**
 * 发放token
 * */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//        super.successfulAuthentication(request, response, chain, authResult);
        SecurityContextHolder.getContext().setAuthentication(authResult);
        UserDetails userDetails = (UserDetails)authResult.getPrincipal();

        String redisJws = redisUtil.getJwt(userDetails.getUsername());
        String token = null;
        boolean isAdmin = userDetails.getAuthorities() != null;
        if (redisJws == null){
            String role = null;
            if (isAdmin){
                role = userDetails.getAuthorities().toArray()[0].toString();
                logger.info("角色权限为:" + role);
            }
            token  = JwtUtil.generateToken(JwtUtil.TokenMessage.builder().username(userDetails.getUsername()).role(role).build());
            redisUtil.createOrUpdateJwt(userDetails.getUsername(),token);
        }else
        {
//            新版本更新， 将redis获取到的过期token 刷新后 重置到redis
            try {
               JwtUtil.parseToken(redisJws);
               token = redisJws;
            }catch (ExpiredJwtException e){
                logger.info("刷新token");
                token = JwtUtil.refreshToken(e);
                redisUtil.createOrUpdateJwt(userDetails.getUsername(),token);
            }
        }
        response.getWriter().write(JSONObject.toJSONString(MvcResult.builder().code(200).message("登录成功").object(token).build()));
    }
/**
 * https://blog.csdn.net/yuanlaijike/article/details/80250389
 *
 * */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException failed) throws IOException, ServletException {
//        super.unsuccessfulAuthentication(request, response, failed);
//        super.onAuthenticationFailure(request, response, exception);
        response.getWriter().write(JSONObject.toJSONString(MvcResult.builder().code(500).message(failed.getMessage()).object(null).build()));
    }
}
